package com.gaj.util;

import com.gaj.entity.message.Message;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * XML解析器工具类
 *
 * 职责：
 * 1. XML文档解析和遍历
 * 2. 实体类映射和实例化
 * 3. 数据类型转换
 * 4. 外键关系处理
 * 5. 批量文件处理
 *
 * 支持结构：
 * - 扁平化结构：将多层嵌套的XML节点扁平化到单个实体类中
 * - 正常结构：XML节点直接对应实体类字段
 * - 数组结构：包含R子节点的数组结构
 *
 * @author 系统
 * @version 1.0
 */
public class ReadXml {

    // ==================== 配置常量 ====================

    /** 默认XML文件路径 */
    private static String xmlPath = "D:\\idea_projects\\gajSpringBoot\\doc\\BJCM政法智能办案平台中心平台项目技术接口标准V4.5.4.2\\标准XML文件下发版\\11_一审公诉_bjcm\\1301E-【法院】结案【检察院、公安、看守所、司法局、监狱】-YWXX.xml";

    /** 根目录路径 */
    private static String rootPath = "D:\\idea_projects\\gajSpringBoot\\doc\\BJCM政法智能办案平台中心平台项目技术接口标准V4.5.4.2\\标准XML文件下发版";

    /** XML元素到实体类的映射配置 */
    public static Properties properties = new Properties();

    // ==================== 静态初始化 ====================

    /**
     * 静态初始化块
     * 加载XML元素到实体类的映射配置文件
     */
    static {
        try {
            ClassLoader classLoader = ReadXml.class.getClassLoader();
            InputStream inputStream = classLoader.getResourceAsStream("xml-entity-mapping.properties");

            if (inputStream != null) {
                properties.load(inputStream);
                //System.out.println("XML实体映射配置文件加载成功，共 " + properties.size() + " 个映射关系");
            } else {
                throw new RuntimeException("配置文件 xml-entity-mapping.properties 未找到");
            }
        } catch (Exception e) {
            System.err.println("加载XML实体映射配置文件失败: " + e.getMessage());
            throw new RuntimeException("加载配置文件失败", e);
        }
    }


    // ==================== 主入口方法 ====================

    /**
     * 主入口方法
     * 用于测试XML解析功能
     *
     * @param args 命令行参数
     */
    public static void main(String[] args) {
        // 批量执行指定文件夹
        // batchExecute(0);

        // 执行单个XML文件
        execute(new File(xmlPath));
    }

    // ==================== 批量处理方法 ====================

    /**
     * 根据序号执行指定文件夹
     *
     * @param index 文件夹序号（从1开始）
     */
    public static void batchExecute(Integer index) {
        File rootFile = new File(rootPath);
        File[] xmlFiles = rootFile.listFiles();

        if (xmlFiles != null && index > 0 && index <= xmlFiles.length) {
            //System.out.println("开始批量处理文件夹: " + xmlFiles[index - 1].getName());
            batchExecute(xmlFiles[index - 1]);
        } else {
            System.err.println("无效的文件夹序号: " + index);
        }
    }

    /**
     * 批量处理指定目录下的所有XML文件
     *
     * @param filePath 目录路径
     */
    public static void batchExecute(File filePath) {
        if (!filePath.exists() || !filePath.isDirectory()) {
            System.err.println("目录不存在或不是有效目录: " + filePath.getPath());
            return;
        }

        File[] xmlFiles = filePath.listFiles();
        if (xmlFiles == null || xmlFiles.length == 0) {
            System.out.println("目录下没有找到XML文件: " + filePath.getPath());
            return;
        }

        //System.out.println("开始批量处理 " + xmlFiles.length + " 个XML文件");

        for (File xmlFile : xmlFiles) {
            if (xmlFile.isFile() && xmlFile.getName().toLowerCase().endsWith(".xml")) {
                //System.out.println("处理XML文件: " + xmlFile.getName());
                execute(xmlFile);
            }
        }

        //System.out.println("批量处理完成");
    }

    // ==================== 单文件处理方法 ====================

    /**
     * 执行单个XML文件解析
     *
     * 执行流程：
     * 1. 使用SAXReader读取XML文件
     * 2. 获取根元素
     * 3. 开始解析XML结构
     *
     * @param xmlFile XML文件对象
     */
    public static void execute(File xmlFile) {
        if (!xmlFile.exists() || !xmlFile.isFile()) {
            System.err.println("XML文件不存在或不是有效文件: " + xmlFile.getPath());
            return;
        }

        try {
            //System.out.println("开始解析XML文件: " + xmlFile.getName());

            // 1. 使用SAXReader读取XML文件
            SAXReader saxReader = new SAXReader();
            Document document = saxReader.read(xmlFile);
            Element rootElement = document.getRootElement();

            // 2. 开始解析XML结构
            parseElement(rootElement);

            //System.out.println("XML文件解析完成: " + xmlFile.getName());

        } catch (Exception e) {
            System.err.println("XML文件解析失败: " + xmlFile.getName() + ", 错误: " + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 执行输入流XML解析
     *
     * 执行流程：
     * 1. 使用SAXReader读取输入流
     * 2. 获取根元素
     * 3. 开始解析XML结构
     *
     * @param inputStream XML输入流
     */
    public static void execute(InputStream inputStream) {
        if (inputStream == null) {
            System.err.println("输入流为空，无法解析XML");
            return;
        }

        try {
            //System.out.println("开始解析XML输入流");

            // 1. 使用SAXReader读取输入流
            SAXReader saxReader = new SAXReader();
            Document document = saxReader.read(inputStream);
            Element rootElement = document.getRootElement();

            // 2. 开始解析XML结构
            parseElement(rootElement);

            //System.out.println("XML输入流解析完成");

        } catch (Exception e) {
            System.err.println("XML输入流解析失败: " + e.getMessage());
            e.printStackTrace();
        }
    }

    // ==================== 核心解析方法 ====================

    /**
     * 递归解析子元素
     *
     * 解析流程：
     * 1. 获取当前元素的所有子元素
     * 2. 递归调用parseElement处理每个子元素
     *
     * @param element XML元素
     */
    private static void parseChildElements(Element element) {
        @SuppressWarnings("unchecked")
        List<Element> childNodes = element.elements();

        for (Element childNode : childNodes) {
            parseElement(childNode);
        }
    }

    /**
     * 解析单个XML元素
     *
     * 解析逻辑：
     * 1. 检查元素是否为实体类节点
     * 2. 如果是实体类节点，解析为实体对象
     * 3. 如果不是实体类节点，递归处理子节点
     *
     * @param element XML元素
     */
    public static void parseElement(Element element) {
        String elementName = element.getName();
        //System.out.println("    解析元素: " + elementName);

        try {
            // 1. 检查是否为实体类节点
            if (isEntityElement(elementName)) {
                //System.out.println("      发现实体类节点: " + elementName);
                parseEntityElement(element);
                return;
            }

            // 2. 如果不是实体类节点，递归处理子节点
            parseChildElements(element);

        } catch (Exception e) {
            System.err.println("      解析元素失败: " + elementName + ", 错误: " + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 解析实体类元素
     *
     * 解析逻辑：
     * 1. 获取元素名称和对应的实体类名
     * 2. 判断是否为数组节点
     * 3. 根据节点类型选择相应的解析方法
     *
     * @param element XML元素
     */
    private static void parseEntityElement(Element element) {
        String elementName = element.getName();
        String entityClassName = getEntityClassName(elementName);

        //System.out.println("      解析实体类元素: " + elementName + " -> " + entityClassName);

        // 判断节点类型并选择相应的解析方法
        if (isArrayNode(element)) {
            //System.out.println("        检测到数组节点，使用数组解析方法");
            parseArrayNode(element, entityClassName);
        } else {
            //System.out.println("        检测到普通节点，使用普通解析方法");
            parseNormalNode(element, entityClassName);
        }
    }

    // ==================== 节点解析方法 ====================

    /**
     * 解析正常节点（非数组节点）
     *
     * 解析流程：
     * 1. 创建实体类实例
     * 2. 遍历子节点，设置字段值
     * 3. 设置外键值
     * 4. 执行数据库插入操作
     *
     * @param element XML元素
     * @param entityClassName 实体类名称
     */
    private static void parseNormalNode(Element element, String entityClassName) {
        try {
            //System.out.println("          开始解析普通节点: " + element.getName());

            // 1. 创建实体类实例
            Class<?> clazz = Class.forName(entityClassName);
            Object instance = clazz.getDeclaredConstructor().newInstance();
            //System.out.println("          创建实体类实例: " + clazz.getSimpleName());

            // 2. 遍历子节点，设置字段值
            @SuppressWarnings("unchecked")
            List<Element> childNodes = element.elements();
            //System.out.println("          发现 " + childNodes.size() + " 个子节点");

            for (Element childNode : childNodes) {
                // 只处理叶子节点（没有子元素的节点）
                if (childNode.elements().isEmpty()) {
                    setFieldValue(instance, childNode.getName(), childNode.getText());
                }
            }

            // 3. 设置外键值
            setXxIdValue(instance);

            // 4. 执行数据库插入操作
            MapperUtils.insertSelective(entityClassName, instance);

            //System.out.println("          普通节点解析完成: " + instance);

        } catch (Exception e) {
            System.err.println("          普通节点解析失败: " + element.getName() + ", 错误: " + e.getMessage());
            e.printStackTrace();
        }
    }
    /**
     * 解析数组节点（包含R子节点的节点）
     *
     * 解析流程：
     * 1. 创建集合存储解析的对象
     * 2. 遍历所有子节点，查找R节点
     * 3. 为每个R节点创建实体实例
     * 4. 解析R节点的所有字段
     * 5. 设置外键值并执行数据库操作
     *
     * @param element XML元素
     * @param entityClassName 实体类名称
     * @return 解析后的对象列表
     */
    public static List<Object> parseArrayNode(Element element, String entityClassName) {
        //System.out.println("          开始解析数组节点: " + element.getName());

        // 1. 创建集合来存储解析的对象
        List<Object> nodeList = new ArrayList<>();

        try {
            Class<?> clazz = Class.forName(entityClassName);
            @SuppressWarnings("unchecked")
            List<Element> childNodes = element.elements();

            System.out.println("          发现 " + childNodes.size() + " 个子节点");

            // 2. 遍历所有子节点，查找R节点
            for (Element childNode : childNodes) {
                if ("R".equals(childNode.getName())) {
                    System.out.println("            处理R节点");

                    // 3. 为每个R节点创建实体实例
                    Object instance = clazz.getDeclaredConstructor().newInstance();

                    // 4. 解析R节点的所有字段
                    parseAllFieldsToEntity(childNode, instance);

                    // 5. 设置外键值
                    setXxIdValue(instance);

                    // 6. 执行数据库插入操作
                    MapperUtils.insertSelective(entityClassName, instance);

                    System.out.println("              R节点解析完成: " + instance);
                    nodeList.add(instance);
                }
            }

            System.out.println("          数组节点解析完成: " + element.getName() + " 包含 " + nodeList.size() + " 个 " + entityClassName + " 对象");

        } catch (Exception e) {
            System.err.println("          数组节点解析失败: " + element.getName() + ", 错误: " + e.getMessage());
            e.printStackTrace();
        }

        return nodeList;
    }

    // ==================== 实体类映射方法 ====================

    /**
     * 获取元素对应的实体类名称
     *
     * 映射逻辑：
     * 从配置文件中查找XML元素名称对应的实体类全限定名
     *
     * @param elementName XML元素名称
     * @return 实体类全限定名，如果不存在返回null
     */
    private static String getEntityClassName(String elementName) {
        String entityClassName = properties.getProperty(elementName);
        if (entityClassName != null) {
            System.out.println("        映射实体类: " + elementName + " -> " + entityClassName);
        }
        return entityClassName;
    }

    /**
     * 判断元素是否对应实体类
     *
     * 判断逻辑：
     * 检查配置文件中是否存在该元素的映射关系
     *
     * @param elementName XML元素名称
     * @return 是否对应实体类
     */
    private static boolean isEntityElement(String elementName) {
        boolean isEntity = getEntityClassName(elementName) != null;
        if (isEntity) {
            System.out.println("        元素 " + elementName + " 是实体类节点");
        }
        return isEntity;
    }

    /**
     * 判断是否是数组节点（包含R子节点）
     *
     * 判断逻辑：
     * 检查子节点中是否存在名为"R"的节点
     *
     * @param element XML元素
     * @return 是否是数组节点
     */
    private static boolean isArrayNode(Element element) {
        @SuppressWarnings("unchecked")
        List<Element> childNodes = element.elements();

        for (Element childNode : childNodes) {
            if ("R".equals(childNode.getName())) {
                System.out.println("        检测到R节点，判定为数组节点");
                return true;
            }
        }

        return false;
    }


    // ==================== 字段解析方法 ====================

    /**
     * 递归解析所有字段到实体类
     *
     * 解析逻辑：
     * 1. 遍历父元素的所有子元素
     * 2. 根据元素类型选择处理方式：
     *    - 实体类节点：单独解析
     *    - 叶子节点：设置字段值
     *    - 容器节点：递归处理
     *
     * @param parent 父元素
     * @param entity 实体类实例
     */
    private static void parseAllFieldsToEntity(Element parent, Object entity) {
        @SuppressWarnings("unchecked")
        List<Element> elements = parent.elements();

        //System.out.println("            开始解析字段，共 " + elements.size() + " 个元素");

        for (Element element : elements) {
            String elementName = element.getName();

            if (isEntityElement(elementName)) {
                // 嵌套的实体类，单独解析
                System.out.println("              发现嵌套实体类: " + elementName);
                parseEntityElement(element);
            } else if (isLeafElement(element)) {
                // 叶子节点，设置字段值
                //System.out.println("              设置字段值: " + elementName + " = " + element.getText());
                setFieldValue(entity, elementName, element.getText());
            } else {
                // 容器节点，递归处理
                System.out.println("              递归处理容器节点: " + elementName);
                parseAllFieldsToEntity(element, entity);
            }
        }
    }

    /**
     * 判断是否是叶子元素（没有子元素）
     *
     * 判断逻辑：
     * 检查元素是否没有子元素
     *
     * @param element XML元素
     * @return 是否是叶子元素
     */
    private static boolean isLeafElement(Element element) {
        boolean isLeaf = element.elements().isEmpty();
        if (isLeaf) {
            //System.out.println("                检测到叶子元素: " + element.getName());
        }
        return isLeaf;
    }

    /**
     * 设置实体类字段值
     *
     * 设置流程：
     * 1. 查找字段
     * 2. 设置字段可访问
     * 3. 转换数据类型
     * 4. 设置字段值
     *
     * @param entity 实体类实例
     * @param fieldName 字段名
     * @param value 字段值
     */
    private static void setFieldValue(Object entity, String fieldName, String value) {
        try {
            // 1. 查找字段
            Field field = findField(entity.getClass(), fieldName);
            if (field != null) {
                // 2. 设置字段可访问
                field.setAccessible(true);

                // 3. 转换数据类型
                Object convertedValue = convertValue(value, field.getType());

                // 4. 设置字段值
                field.set(entity, convertedValue);

                //System.out.println("                字段设置成功: " + fieldName + " = " + convertedValue);
            } else {
                //System.err.println("                未找到字段: " + fieldName + " 在类: " + entity.getClass().getSimpleName());
            }
        } catch (Exception e) {
            System.err.println("                设置字段值失败: " + fieldName + " = " + value + ", 错误: " + e.getMessage());
        }
    }

    // ==================== 外键处理方法 ====================

    /**
     * 设置实体类外键值
     *
     * 设置逻辑：
     * 1. 获取表主键ID缓存
     * 2. 遍历所有外键
     * 3. 查找对应的字段并设置值
     *
     * @param entity 实体类实例
     */
    private static void setXxIdValue(Object entity) {
        HashMap<String, Integer> tableIds = MapperUtils.tableIds;
        System.out.println("            开始设置外键值，共 " + tableIds.size() + " 个外键");

        for (String idName : tableIds.keySet()) {
            // idName是外键名称
            String idValue = String.valueOf(tableIds.get(idName));
            Field field = findField(entity.getClass(), idName);

            try {
                if (field != null) {
                    field.setAccessible(true);
                    Object convertedValue = convertValue(idValue, field.getType());
                    field.set(entity, convertedValue);
                    System.out.println("              外键设置成功: " + idName + " = " + convertedValue);
                } else {
                    //System.out.println("              未找到外键字段: " + idName + " 在类: " + entity.getClass().getSimpleName());
                }
            } catch (Exception e) {
                System.err.println("              设置外键值失败: " + idName + " = " + idValue + ", 错误: " + e.getMessage());
            }
        }
    }

    // ==================== 字段查找方法 ====================

    /**
     * 查找字段（支持驼峰命名转换）
     *
     * 查找逻辑：
     * 1. 尝试直接匹配字段名（小写）
     * 2. 如果找不到，返回null
     *
     * @param clazz 类
     * @param fieldName 字段名
     * @return 字段对象，如果找不到返回null
     */
    private static Field findField(Class<?> clazz, String fieldName) {
        try {
            // 尝试直接匹配（小写）
            Field field = clazz.getDeclaredField(fieldName.toLowerCase());
            //System.out.println("                找到字段: " + fieldName + " -> " + field.getName());
            return field;
        } catch (NoSuchFieldException e) {
            //System.out.println("                未找到字段: " + fieldName + " 在类: " + clazz.getSimpleName());
            return null;
        }
    }

    /**
     * 转换为驼峰命名
     *
     * 转换逻辑：
     * 1. 检查输入是否为空
     * 2. 将首字母转换为小写
     * 3. 保持其余字母不变
     *
     * @param str 输入字符串
     * @return 驼峰命名字符串
     */
    @SuppressWarnings("unused")
    private static String toCamelCase(String str) {
        if (str == null || str.isEmpty()) {
            return str;
        }
        return str.substring(0, 1).toLowerCase() + str.substring(1);
    }




    /**
     * 读取FTP文件并解析XML，提取CFLJ字段（压缩包名）
     *
     * 解析流程：
     * 1. 获取XML文件的输入流
     * 2. 使用dom4j解析XML文档
     * 3. 解析为Message对象
     * 4. 提取CFLJ字段（压缩包名）
     * 5. 完成文件传输操作
     *
     * @param ftpClient FTP客户端实例
     * @param nodePath 节点路径
     * @param xmlFile XML文件对象
     * @return CFLJ字段值，解析失败返回null
     */
    public static String readXmlFile(FTPClient ftpClient, String nodePath, FTPFile xmlFile) {
        //System.out.println("            开始读取XML文件: " + xmlFile.getName());

        try (InputStream inputStream = FtpUtils.getFileInputStream(ftpClient, nodePath, xmlFile.getName())) {
            // 1. 检查输入流是否有效
            if (inputStream == null) {
                System.out.println("            无法获取XML文件输入流");
                return null;
            }

            // 2. 使用dom4j解析XML文档
            SAXReader saxReader = new SAXReader();
            Document document = saxReader.read(inputStream);
            Element rootElement = document.getRootElement();

            // 3. 解析为Message对象并获取CFLJ字段
            Message message = (Message) ReadXml.parseArrayNode(rootElement, "com.gaj.entity.message.Message").get(0);
            String cflj = message.getCflj();

            //System.out.println("            XML文件解析成功，CFLJ字段: " + cflj);
            return cflj;

        } catch (Exception e) {
            System.err.println("            读取XML文件失败: " + xmlFile.getName() + ", 错误: " + e.getMessage());
            return null;
        } finally {
            // 4. 完成文件传输操作，释放FTP连接资源
            FtpUtils.completeFileTransfer(ftpClient);
        }
    }






    // ==================== 类型转换方法 ====================

    /**
     * 根据字段类型转换元素类型
     *
     * 转换逻辑：
     * 1. 检查输入值是否为空
     * 2. 根据字段类型进行相应的类型转换
     * 3. 处理转换异常
     *
     * 支持的类型：
     * - 基本数据类型：int, long, short, byte, char, boolean, float, double
     * - 包装类型：Integer, Long, Short, Byte, Character, Boolean, Float, Double
     * - 字符串类型：String
     * - 数值类型：BigDecimal
     * - 日期时间类型：Date, LocalDate, LocalTime, LocalDateTime
     *
     * @param textValue 文本值
     * @param fieldType 字段类型
     * @return 转换后的值
     */
    public static Object convertValue(String textValue, Class<?> fieldType) {
        // 1. 检查输入值是否为空
        if (textValue == null || textValue.trim().isEmpty()) {
            System.out.println("                " + fieldType.getSimpleName() + "输入值为空，返回null");
            return null;
        }

        try {
            //System.out.println("                开始类型转换: " + textValue + " -> " + fieldType.getSimpleName());

            // 2. 根据字段类型进行相应的类型转换
            if (fieldType == int.class || fieldType == Integer.class) {
                return Integer.valueOf(textValue);
            }
            else if (fieldType == String.class) {
                return textValue;
            }
            else if (fieldType == boolean.class || fieldType == Boolean.class) {
                return Boolean.valueOf(textValue);
            }
            else if (fieldType == double.class || fieldType == Double.class) {
                return Double.valueOf(textValue);
            }
            else if (fieldType == float.class || fieldType == Float.class) {
                return Float.valueOf(textValue);
            }
            else if (fieldType == long.class || fieldType == Long.class) {
                return Long.valueOf(textValue);
            }
            else if (fieldType == short.class || fieldType == Short.class) {
                return Short.valueOf(textValue);
            }
            else if (fieldType == byte.class || fieldType == Byte.class) {
                return Byte.valueOf(textValue);
            }
            else if (fieldType == char.class || fieldType == Character.class) {
                return textValue.charAt(0);
            }
            else if (fieldType == BigDecimal.class) {
                return new BigDecimal(textValue);
            }
            else if (fieldType == Date.class) {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                try {
                    return sdf.parse(textValue);
                } catch (ParseException e) {
                    System.err.println("                日期解析失败，使用默认值: 1000-01-01");
                    return "1000-01-01";
                }
            }
            else if (fieldType == LocalDate.class) {
                return LocalDate.parse(textValue, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
            }
            else if (fieldType == LocalTime.class) {
                return LocalTime.parse(textValue, DateTimeFormatter.ofPattern("HH:mm:ss"));
            }
            else if (fieldType == LocalDateTime.class) {
                return LocalDateTime.parse(textValue, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            }
            else {
                // 其他类型，直接返回字符串
                System.out.println("                未知类型，返回原字符串");
                return textValue;
            }
        } catch (Exception e) {
            System.err.println("                类型转换失败: " + textValue + " -> " + fieldType.getSimpleName() + ", 错误: " + e.getMessage());
            return null;
        }
    }

    /**
     * 获取字段类型的默认值
     * @param fieldType 字段类型
     * @return 默认值
     */
    public static Object getDefaultValue(Class<?> fieldType) {
        if (fieldType == int.class || fieldType == Integer.class) {
            return 0;
        } else if (fieldType == String.class) {
            return "";
        } else if (fieldType == boolean.class || fieldType == Boolean.class) {
            return false;
        } else if (fieldType == double.class || fieldType == Double.class) {
            return 0.0;
        } else if (fieldType == float.class || fieldType == Float.class) {
            return 0.0f;
        } else if (fieldType == long.class || fieldType == Long.class) {
            return 0L;
        } else if (fieldType == short.class || fieldType == Short.class) {
            return (short) 0;
        } else if (fieldType == byte.class || fieldType == Byte.class) {
            return (byte) 0;
        } else if (fieldType == char.class || fieldType == Character.class) {
            return '\0';
        } else if (fieldType == BigDecimal.class) {
            return BigDecimal.ZERO;
        } else if (fieldType == Date.class) {
            return new Date();
        } else if (fieldType == LocalDate.class) {
            return LocalDate.now();
        } else if (fieldType == LocalTime.class) {
            return LocalTime.now();
        } else if (fieldType == LocalDateTime.class) {
            return LocalDateTime.now();
        } else {
            return null;
        }
    }
}
